import {
  PipeTransform,
  Injectable,
  ArgumentMetadata,
  BadRequestException,
  ValidationPipe as NestValidationPipe
} from '@nestjs/common'
import type { ValidationPipeOptions } from '@nestjs/common'
import { validate, ValidationError } from 'class-validator'
import { plainToClass } from 'class-transformer'
import { LoggerService } from '../logger/logger.service'

/**
 * 自定义验证管道
 * 扩展了NestJS默认的ValidationPipe，增加了更详细的错误处理和日志记录
 */
@Injectable()
export class CustomValidationPipe extends NestValidationPipe {
  constructor(
    private readonly logger: LoggerService,
    options?: ValidationPipeOptions
  ) {
    super({
      // 自动转换类型
      transform: true,
      // 去除未定义的属性
      whitelist: true,
      // 禁止额外属性
      forbidNonWhitelisted: true,
      // 禁用详细错误（生产环境）
      disableErrorMessages: process.env.NODE_ENV === 'production',
      // 验证组
      groups: [],
      // 总是验证
      skipMissingProperties: false,
      // 跳过空值验证
      skipNullProperties: false,
      // 跳过未定义值验证
      skipUndefinedProperties: false,
      // 自定义错误工厂
      exceptionFactory: (errors: ValidationError[]) => {
        return this.createValidationException(errors)
      },
      ...options
    })
  }

  /**
   * 创建验证异常
   * @param errors 验证错误数组
   * @returns BadRequestException
   */
  private createValidationException(errors: ValidationError[]): BadRequestException {
    const formattedErrors = this.formatValidationErrors(errors)

    // 记录验证错误日志
    this.logger.warn(
      '表单验证失败',
      JSON.stringify({
        errors: formattedErrors,
        errorCount: errors.length
      })
    )

    return new BadRequestException({
      statusCode: 400,
      error: 'Bad Request',
      message: '表单验证失败',
      details: formattedErrors,
      timestamp: new Date().toISOString()
    })
  }

  /**
   * 格式化验证错误
   * @param errors 验证错误数组
   * @returns 格式化后的错误对象
   */
  private formatValidationErrors(errors: ValidationError[]): Record<string, any> {
    const formattedErrors: Record<string, any> = {}

    errors.forEach(error => {
      const field = error.property
      const constraints = error.constraints || {}
      const children = error.children || []

      if (Object.keys(constraints).length > 0) {
        // 直接约束错误
        formattedErrors[field] = {
          value: error.value,
          errors: Object.values(constraints),
          constraints: constraints
        }
      }

      if (children.length > 0) {
        // 嵌套对象错误
        const nestedErrors = this.formatValidationErrors(children)
        formattedErrors[field] = {
          value: error.value,
          children: nestedErrors
        }
      }
    })

    return formattedErrors
  }
}

/**
 * 文件验证管道
 * 专门用于文件上传的验证
 */
@Injectable()
export class FileValidationPipe implements PipeTransform {
  constructor(private readonly logger?: LoggerService) {}

  async transform(value: any, metadata: ArgumentMetadata) {
    if (!value) {
      throw new BadRequestException('文件不能为空')
    }

    // 单文件验证
    if (value.mimetype) {
      return this.validateSingleFile(value)
    }

    // 多文件验证
    if (Array.isArray(value)) {
      return Promise.all(value.map(file => this.validateSingleFile(file)))
    }

    throw new BadRequestException('无效的文件格式')
  }

  /**
   * 验证单个文件
   * @param file 文件对象
   * @returns 验证后的文件对象
   */
  private async validateSingleFile(file: Express.Multer.File): Promise<Express.Multer.File> {
    const errors: string[] = []

    // 文件大小验证（默认10MB）
    const maxSize = parseInt(process.env.MAX_FILE_SIZE || '10485760', 10)
    if (file.size > maxSize) {
      errors.push(`文件大小不能超过 ${this.formatFileSize(maxSize)}`)
    }

    // 文件类型验证
    const allowedMimeTypes = [
      // 图片
      'image/jpeg',
      'image/png',
      'image/gif',
      'image/webp',
      'image/svg+xml',
      // 文档
      'application/pdf',
      'application/msword',
      'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
      'application/vnd.ms-excel',
      'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
      'application/vnd.ms-powerpoint',
      'application/vnd.openxmlformats-officedocument.presentationml.presentation',
      // 文本
      'text/plain',
      'text/csv',
      // 压缩文件
      'application/zip',
      'application/x-rar-compressed',
      'application/x-7z-compressed',
      // 音频
      'audio/mpeg',
      'audio/wav',
      'audio/ogg',
      // 视频
      'video/mp4',
      'video/avi',
      'video/quicktime',
      'video/x-msvideo'
    ]

    if (!allowedMimeTypes.includes(file.mimetype)) {
      errors.push(`不支持的文件类型: ${file.mimetype}`)
    }

    // 文件名验证
    if (!file.originalname || file.originalname.trim().length === 0) {
      errors.push('文件名不能为空')
    }

    // 文件名长度验证
    if (file.originalname && file.originalname.length > 255) {
      errors.push('文件名长度不能超过255个字符')
    }

    // 危险文件扩展名检查
    const dangerousExtensions = [
      '.exe',
      '.bat',
      '.cmd',
      '.com',
      '.pif',
      '.scr',
      '.vbs',
      '.js',
      '.jar',
      '.php',
      '.asp',
      '.aspx',
      '.jsp',
      '.sh',
      '.ps1'
    ]

    const fileExtension = this.getFileExtension(file.originalname).toLowerCase()
    if (dangerousExtensions.includes(fileExtension)) {
      errors.push(`危险的文件扩展名: ${fileExtension}`)
    }

    if (errors.length > 0) {
      this.logger?.warn(
        '文件验证失败',
        JSON.stringify({
          filename: file.originalname,
          mimetype: file.mimetype,
          size: file.size,
          errors
        })
      )

      throw new BadRequestException({
        statusCode: 400,
        error: 'Bad Request',
        message: '文件验证失败',
        details: {
          filename: file.originalname,
          errors
        },
        timestamp: new Date().toISOString()
      })
    }

    this.logger?.debug(
      '文件验证通过',
      JSON.stringify({
        filename: file.originalname,
        mimetype: file.mimetype,
        size: file.size
      })
    )

    return file
  }

  /**
   * 获取文件扩展名
   * @param filename 文件名
   * @returns 文件扩展名
   */
  private getFileExtension(filename: string): string {
    const lastDotIndex = filename.lastIndexOf('.')
    return lastDotIndex !== -1 ? filename.substring(lastDotIndex) : ''
  }

  /**
   * 格式化文件大小
   * @param bytes 字节数
   * @returns 格式化后的文件大小
   */
  private formatFileSize(bytes: number): string {
    const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB']
    if (bytes === 0) return '0 Bytes'
    const i = Math.floor(Math.log(bytes) / Math.log(1024))
    return Math.round((bytes / Math.pow(1024, i)) * 100) / 100 + ' ' + sizes[i]
  }
}

/**
 * 查询参数验证管道
 * 专门用于查询参数的验证和转换
 */
@Injectable()
export class QueryValidationPipe implements PipeTransform {
  constructor(private readonly logger: LoggerService) {}

  async transform(value: any, metadata: ArgumentMetadata) {
    if (!metadata.metatype || !this.toValidate(metadata.metatype)) {
      return value
    }

    // 转换查询参数
    const object = plainToClass(metadata.metatype, value)

    // 验证查询参数
    const errors = await validate(object, {
      whitelist: true,
      forbidNonWhitelisted: true,
      transform: true,
      skipMissingProperties: false
    })

    if (errors.length > 0) {
      const formattedErrors = this.formatValidationErrors(errors)

      this.logger.warn(
        '查询参数验证失败',
        JSON.stringify({
          query: value,
          errors: formattedErrors
        })
      )

      throw new BadRequestException({
        statusCode: 400,
        error: 'Bad Request',
        message: '查询参数验证失败',
        details: formattedErrors,
        timestamp: new Date().toISOString()
      })
    }

    return object
  }

  /**
   * 检查是否需要验证
   * @param metatype 元类型
   * @returns 是否需要验证
   */
  private toValidate(metatype: Function): boolean {
    const types: Function[] = [String, Boolean, Number, Array, Object]
    return !types.includes(metatype)
  }

  /**
   * 格式化验证错误
   * @param errors 验证错误数组
   * @returns 格式化后的错误对象
   */
  private formatValidationErrors(errors: ValidationError[]): Record<string, any> {
    const formattedErrors: Record<string, any> = {}

    errors.forEach(error => {
      const field = error.property
      const constraints = error.constraints || {}

      if (Object.keys(constraints).length > 0) {
        formattedErrors[field] = {
          value: error.value,
          errors: Object.values(constraints)
        }
      }
    })

    return formattedErrors
  }
}

/**
 * 分页验证管道
 * 专门用于分页参数的验证
 */
@Injectable()
export class PaginationValidationPipe implements PipeTransform {
  transform(value: any, metadata: ArgumentMetadata) {
    if (metadata.type !== 'query') {
      return value
    }

    const result = { ...value }

    // 页码验证和转换
    if (result.page !== undefined) {
      const page = parseInt(result.page, 10)
      if (isNaN(page) || page < 1) {
        throw new BadRequestException('页码必须是大于0的整数')
      }
      result.page = page
    } else {
      result.page = 1
    }

    // 每页数量验证和转换
    if (result.limit !== undefined) {
      const limit = parseInt(result.limit, 10)
      if (isNaN(limit) || limit < 1) {
        throw new BadRequestException('每页数量必须是大于0的整数')
      }
      if (limit > 100) {
        throw new BadRequestException('每页数量不能超过100')
      }
      result.limit = limit
    } else {
      result.limit = 10
    }

    // 排序字段验证
    if (result.sortBy && typeof result.sortBy !== 'string') {
      throw new BadRequestException('排序字段必须是字符串')
    }

    // 排序方向验证
    if (result.sortOrder && !['ASC', 'DESC', 'asc', 'desc'].includes(result.sortOrder)) {
      throw new BadRequestException('排序方向必须是ASC或DESC')
    }

    return result
  }
}

/**
 * 全局验证管道配置
 */
export const globalValidationPipeConfig: ValidationPipeOptions = {
  // 自动转换类型
  transform: true,
  // 去除未定义的属性
  whitelist: true,
  // 禁止额外属性
  forbidNonWhitelisted: true,
  // 生产环境禁用详细错误
  disableErrorMessages: process.env.NODE_ENV === 'production',
  // 验证组
  groups: [],
  // 总是验证
  skipMissingProperties: false,
  // 跳过空值验证
  skipNullProperties: false,
  // 跳过未定义值验证
  skipUndefinedProperties: false,
  // 验证每个项目（数组）
  validateCustomDecorators: true,
  // 错误消息详细程度
  dismissDefaultMessages: false,
  // 验证未知对象
  validationError: {
    target: false,
    value: false
  }
}
